import atexit
import os
import logging
import json
from datetime import datetime
from logging.handlers import RotatingFileHandler
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail, Message
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import (
    LoginManager,
    UserMixin,
    login_user,
    logout_user,
    login_required,
    current_user,
)
from apscheduler.schedulers.background import BackgroundScheduler
from pysnmp.hlapi.v3arch.asyncio import (
    SnmpEngine,
    CommunityData,
    UdpTransportTarget,
    ContextData,
    ObjectType,
    ObjectIdentity,
    set_cmd,
)

# from flask_wtf.csrf import CSRFProtect

from models import Device, DeviceGroup, DeviceHistory, User  # 数据库模型
from utils.snmp_tool import SNMPClient  # SNMP控制客户端
from utils.paramiko_tool import ControllerClient  # 控制器客户端
from utils.tools import *  # 控制器工具类
from extensions import db, mail, login_manager

# 配置日志
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
handler = RotatingFileHandler("app.log", maxBytes=10000, backupCount=3)
logger.addHandler(handler)

# 初始化Flask应用
app = Flask(__name__)
app.secret_key = os.urandom(24)

# 数据库配置
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(
    app.instance_path, "devices.db"
)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

# 邮件配置（示例配置，需替换为真实值）
app.config["MAIL_SERVER"] = "smtp.qq.com"
app.config["MAIL_PORT"] = 465
app.config["MAIL_USE_SSL"] = True
app.config["MAIL_USERNAME"] = "1726557368@qq.com"
app.config["MAIL_PASSWORD"] = "Zwq!!08280819"
app.config["MAIL_DEFAULT_SENDER"] = "1726557368@qq.com"

# 初始化扩展
mail.init_app(app)
db.init_app(app)
login_manager.init_app(app)
login_manager.login_view = "login"

# 在创建app后初始化CSRF保护
# csrf = CSRFProtect(app)

# 确保设置了密钥（用于CSRF和会话安全）
# app.secret_key = "your-secret-key-here"  # 生产环境应该使用更安全的密钥

# 确保实例文件夹存在
os.makedirs(app.instance_path, exist_ok=True)


# 登录管理器设置
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


# 初始化数据库
with app.app_context():
    db.create_all()

    # 添加默认管理员用户（如果不存在）
    if not User.query.filter_by(username="admin").first():
        admin = User(
            username="admin", password=generate_password_hash("admin123"), is_admin=True
        )
        db.session.add(admin)

    # 添加测试设备（如果不存在）
    if not Device.query.first():
        db.session.add_all(
            [
                Device(ip="192.168.1.1", name="核心交换机"),
                Device(ip="192.168.1.2", name="接入交换机", status="离线"),
                Device(ip="192.168.1.3", name="防火墙"),
            ]
        )
    db.session.commit()


# 邮件告警功能
def send_alert(device, metric, value):
    try:
        msg = Message(
            subject=f"设备告警: {device.name}",
            recipients=["admin@example.com"],
            body=f"""
            设备 {device.name}({device.ip}) 触发告警:
            指标: {metric}
            当前值: {value}
            时间: {datetime.now()}
            """,
        )
        mail.send(msg)
        logger.info(f"已发送告警邮件: {device.name} {metric}={value}")
    except Exception as e:
        logger.error(f"发送告警邮件失败: {str(e)}")


def send_flow_table(ip, community, flow_rules):
    try:
        ODL_FLOW_OID = "1.3.6.1.4.1.8072.9999.1.1"
        utt = UdpTransportTarget.create((ip, 161))

        for rule in flow_rules:
            errorIndication, errorStatus, errorIndex, varBinds = next(
                set_cmd(
                    SnmpEngine(),
                    CommunityData(community),
                    utt,
                    ContextData(),
                    ObjectType(ObjectIdentity(ODL_FLOW_OID), str(rule)),
                )
            )

            if errorIndication or errorStatus:  # 确保这行缩进正确
                logger.error(f"SNMP错误: {errorIndication or errorStatus}")
                return False
        return True
    except Exception as e:
        return False


# 设备状态更新
def update_devices():
    with app.app_context():
        logger.info("开始设备状态更新...")
        online_count = alert_count = 0

        for device in Device.query.filter_by(is_monitored=True).all():
            try:
                data = SNMPClient.get_system_info(device.ip, device.snmp_community)

                # 更新设备状态
                device.status = "在线"
                device.cpu_usage = data.get("cpu", 0)
                device.last_seen = datetime.now()
                online_count += 1

                # 触发告警
                if device.cpu_usage > 90:
                    alert_count += 1
                    send_alert(device, "CPU使用率", device.cpu_usage)

                db.session.commit()

            except Exception as e:
                device.status = "离线"
                device.cpu_usage = 0
                db.session.commit()
                logger.error(
                    f"设备 {device.ip} 更新失败: {str(e)}"
                )  # 确保这行缩进与上面的except对齐

        app.config["ONLINE_COUNT"] = online_count
        app.config["ALERT_COUNT"] = alert_count


# 定时任务配置
scheduler = BackgroundScheduler()
scheduler.add_job(update_devices, "interval", minutes=5)
scheduler.start()
atexit.register(lambda: scheduler.shutdown())


# 辅助函数
def validate_ip(ip):
    parts = ip.split(".")
    if len(parts) != 4:
        return False
    try:
        return all(0 <= int(part) <= 255 for part in parts)
    except ValueError:
        return False


# 路由定义
# 正确定义index路由（两种URL指向同一个页面）
@app.route("/")
@app.route("/index")
@app.route("/dashboard")
@login_required
def index():
    devices = Device.query.all()
    online_count = Device.query.filter_by(status="在线").count()
    alert_count = Device.query.filter(Device.cpu_usage > 90).count()

    return render_template(
        "index.html",
        devices=devices,
        online_count=online_count,
        alert_count=alert_count,
        total_count=len(devices),
    )


# @app.route("/dashboard")
# @login_required
# def dashboard():
#     devices = Device.query.all()
#     online_count = Device.query.filter_by(status="在线").count()
#     alert_count = Device.query.filter(Device.cpu_usage > 90).count()
#     return render_template(
#         "index.html",
#         devices=devices,
#         online_count=online_count,
#         alert_count=alert_count,
#         total_count=len(devices),
#     )


# 设备状态API
@app.route("/api/devices/status")
@login_required
def device_status_api():
    devices = Device.query.all()
    return jsonify(
        [{"id": d.id, "status": d.status, "cpu": d.cpu_usage} for d in devices]
    )


# 批量操作接口
@app.route("/api/devices/batch", methods=["POST"])
@login_required
def batch_operations():
    operation = request.json.get("op")  # refresh/reboot等
    # 实现逻辑...


@app.route("/test_snmp", methods=["POST"])
@login_required
def test_snmp():
    data = request.get_json()
    try:
        info = SNMPClient.get_system_info(data["ip"], data["community"])
        return jsonify(
            {"success": True, "device_name": info["name"], "cpu_usage": info["cpu"]}
        )
    except Exception as e:
        return jsonify({"success": False, "message": str(e)}), 400


@app.route("/login", methods=["GET", "POST"])
def login():
    # 如果用户已经登录，直接跳转到主页面
    if current_user.is_authenticated:
        return redirect(url_for("index"))  # 跳转到名为'index'的路由

    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        remember = request.form.get("remember", "off") == "on"

        # 验证用户
        user = User.query.filter_by(username=username).first()
        if user and check_password_hash(user.password, password):
            login_user(user, remember=remember)

            # 安全处理next参数（防止开放重定向攻击）
            next_page = request.args.get("next")
            if not next_page or not next_page.startswith("/"):
                next_page = url_for("index")  # 默认跳转到主页面

            return redirect(url_for("index"))

        flash("用户名或密码错误", "danger")

    return render_template("login.html")


@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for("login"))


@app.route("/add", methods=["GET", "POST"])
@login_required
def add_device():
    groups = DeviceGroup.query.all()  # Ensure groups is always defined
    if request.method == "POST":
        ip = request.form["ip"]
        name = request.form["name"]
        username = request.form["username"]
        passwd = request.form["passwd"]
        snmp_community = request.form.get("snmp_community", "public")
        device_type = request.form.get("device_type", "switch")
        enable_flow = request.form.get("enable_flow") == "on"
        enable_monitoring = request.form.get("enable_monitoring") == "on"
        group_id = request.form.get("group_id") or None
        if not validate_ip(ip):
            flash("无效的IP地址格式", "danger")
        elif Device.query.filter_by(ip=ip).first():
            flash("该IP地址已存在", "danger")
        else:
            try:
                device = Device(
                    ip=ip,
                    name=name,
                    username=username,
                    passwd=passwd,
                    snmp_community=snmp_community,
                    device_type=device_type,
                )
                db.session.add(device)
                db.session.commit()

                # 初始流表下发
                flow_rules = [
                    {
                        "table_id": 0,
                        "priority": 100,
                        "match": {"ipv4_destination": f"{ip}/32"},
                        "actions": ["output=CONTROLLER"],
                    }
                ]
                send_flow_table(ip, snmp_community, flow_rules)

                flash("设备添加成功", "success")
                return redirect(url_for("dashboard"))
            except Exception as e:
                db.session.rollback()
                logger.error(f"添加设备失败: {str(e)}")
                flash(f"添加设备失败: {str(e)}", "danger")
    return render_template("add_device.html", groups=groups)


@app.route("/edit/<int:id>", methods=["GET", "POST"])
@login_required
def edit_device(id):
    device = Device.query.get_or_404(id)
    if request.method == "POST":
        device.ip = request.form["ip"]
        device.name = request.form["name"]
        device.snmp_community = request.form.get("snmp_community", "public")
        device.device_type = request.form.get("device_type", "switch")

        # 示例流表规则
        flow_rules = [
            {
                "table_id": 0,
                "priority": 100,
                "match": {"ipv4_destination": f"{device.ip}/32"},
                "actions": ["output=CONTROLLER"],
            }
        ]

        try:
            if send_flow_table(device.ip, device.snmp_community, flow_rules):
                flash("设备更新且流表下发成功", "success")
            else:
                flash("设备更新成功，但流表下发失败", "warning")
        except Exception as e:
            flash(f"流表下发出错: {str(e)}", "danger")

        db.session.commit()
        return redirect(url_for("dashboard"))

    return render_template("edit.html", device=device)


@app.route("/delete/<int:id>")
@login_required
def delete_device(id):
    device = Device.query.get_or_404(id)
    try:
        db.session.delete(device)
        db.session.commit()
        flash("设备删除成功", "success")
    except Exception as e:
        db.session.rollback()
        logger.error(f"删除设备失败: {str(e)}")
        flash("删除设备失败", "danger")
    return redirect(url_for("index"))


@app.route("/update")
@login_required
def manual_update():
    update_devices()
    flash("设备状态已手动更新", "info")
    return redirect(url_for("index"))


@app.route("/groups")
@login_required
def group_list():
    groups = DeviceGroup.query.all()  # 这行缩进应该与函数体内的其他语句一致
    return render_template("groups.html", groups=groups)


@app.route("/group/<int:id>")
@login_required
def group_detail(id):
    group = DeviceGroup.query.get_or_404(id)
    available_devices = Device.query.filter_by(group_id=None).all()
    return render_template(
        "group_detail.html", group=group, available_devices=available_devices
    )


@app.route("/report/<int:device_id>")
@login_required
def device_report(device_id):
    device = Device.query.get_or_404(device_id)
    history = (
        DeviceHistory.query.filter_by(device_id=device.id)
        .order_by(DeviceHistory.timestamp.desc())
        .limit(24)
        .all()
    )

    timestamps = [h.timestamp.strftime("%H:%M") for h in history]
    cpu_data = [h.cpu_usage for h in history]

    return render_template(
        "report.html", device=device, timestamps=timestamps, cpu_data=cpu_data
    )


@app.route("/network/<int:device_id>")
@login_required
def network_report(device_id):
    device = Device.query.get_or_404(device_id)
    try:
        dc = ControllerClient(
            name=device.name,
            ip=device.ip,
            username=device.username,
            passwd=device.passwd,
        )
        _, stdout, _ = dc.call("display ip routing-table")
        dc.close()
        routing_table = parse_route_table(stdout)

        return render_template(
            "network.html",
            device_name=device.name,
            routing_table=routing_table,
        )
        # return jsonify(
        #     {
        #         "success": True,
        #         "device_name": device.name,
        #         "routing_table": parse_route_table(stdout),
        #     }
        # )
    except Exception as e:
        return jsonify({"success": False, "message": str(e)}), 400


@app.route("/control/<int:device_id>", methods=["POST"])
@login_required
def device_control(device_id):
    device = Device.query.get_or_404(device_id)
    if request.method == "POST":
        command = request.form["command"]

    try:
        dc = ControllerClient(
            name=device.name,
            ip=device.ip,
            username=device.username,
            passwd=device.passwd,
        )
        _, _, _ = dc.call("system-view")
        _, stdout, _ = dc.call(command)
        dc.close()

        return jsonify({"success": True, "device_name": device.name, "result": stdout})
    except Exception as e:
        return jsonify({"success": False, "message": str(e)}), 400


@app.route("/topology")
@login_required
def topology():
    online_devices = Device.query.filter_by(status="在线").all()
    devices_data = {}

    for device in online_devices:
        dc = ControllerClient(
            name=device.name,
            ip=device.ip,
            username=device.username,
            passwd=device.passwd,
        )
        _, stdout, _ = dc.call("display ip routing-table")
        dc.close()
        devices_data.update({device.name: parse_route_table(stdout)})

    return render_template("topo.html", devices_data=devices_data)


if __name__ == "__main__":
    with app.app_context():
        db.create_all()
        # 添加默认管理员用户（如果不存在）
        if not User.query.filter_by(username="admin").first():
            admin = User(
                username="admin",
                password=generate_password_hash("admin123"),
                is_admin=True,
            )
            db.session.add(admin)
        # 添加测试设备（如果不存在）
        if not Device.query.first():
            db.session.add_all(
                [
                    Device(ip="192.168.1.1", name="核心交换机"),
                    Device(ip="192.168.1.2", name="接入交换机", status="离线"),
                    Device(ip="192.168.1.3", name="防火墙"),
                ]
            )
        db.session.commit()
    app.run(debug=True, host="0.0.0.0", port=5001)
